<script>
import Loading from '@shell/components/Loading';
import CreateEditView from '@shell/mixins/create-edit-view';
import FormValidation from '@shell/mixins/form-validation';
import { _CREATE } from '@shell/config/query-params';
import { stringify } from '@shell/utils/error';
import { Banner } from '@components/Banner';
import merge from 'lodash/merge';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import { LabeledInput } from '@components/Form/LabeledInput';
import { Checkbox } from '@components/Form/Checkbox';
import ArrayList from '@shell/components/form/ArrayList';
import GCEImage from '@shell/machine-config/components/GCEImage.vue';
import { convertStringToKV, convertKVToString } from '@shell/utils/object';
import KeyValue from '@shell/components/form/KeyValue';
import {
  getGKEZones, getGKEDiskTypes, getGKENetworks, getGKEMachineTypes, getGKESubnetworks, getGKESharedSubnetworks
} from '@shell/components/google/util/gcp';
import { formatSharedNetworks, formatNetworkOptions, formatSubnetworkOptions } from '@shell/components/google/util/formatter';
import { mapGetters } from 'vuex';
import { sortBy, sortableNumericSuffix } from '@shell/utils/sort';
const GKE_NONE_OPTION = 'none';

const DEFAULT_MIN_DISK = 10;

const defaultConfig = Object.freeze({
  zone:                          'us-central1-a',
  machineImage:                  '',
  diskType:                      'pd-standard',
  network:                       '',
  subnetwork:                    '',
  scopes:                        'https://www.googleapis.com/auth/devstorage.read_only,https://www.googleapis.com/auth/logging.write,https://www.googleapis.com/auth/monitoring.write',
  machineType:                   'n1-standard-2',
  diskSize:                      '50',
  tags:                          '',
  address:                       '',
  openPort:                      [],
  vmLabels:                      '',
  username:                      'docker-user',
  setInternalFirewallRulePrefix: true,
  setExternalFirewallRulePrefix: false,
  preemptible:                   false
});

export default {
  emits: ['expandAdvanced', 'error', 'validationChanged'],

  components: {
    ArrayList,
    Banner,
    Checkbox,
    KeyValue,
    LabeledInput,
    LabeledSelect,
    Loading,
    GCEImage
  },

  mixins: [CreateEditView, FormValidation],

  props: {
    credentialId: {
      type:     String,
      required: true,
    },
    projectId: {
      type:    String,
      default: null,
    },
    mode: {
      type:    String,
      default: _CREATE,
    },
    uuid: {
      type:     String,
      required: true,
    },

    disabled: {
      type:    Boolean,
      default: false
    }
  },

  async fetch() {
    if ( !this.credentialId ) {
      return;
    }

    for (const key in this.defaultConfig) {
      if (this.value[key] === undefined && !!this.defaultConfig[key]) {
        this.value[key] = this.defaultConfig[key];
      }
    }
    await Promise.all([this.getZones(), this.getOptions()]);
  },

  data() {
    return {
      defaultConfig,
      loadingZones:                 false,
      loadingDiskTypes:             false,
      loadingNetworks:              false,
      loadingMachineTypes:          false,
      zones:                        [],
      diskTypes:                    [],
      networks:                     [],
      subnetworks:                  [],
      sharedSubnetworks:            [],
      machineTypes:                 [],
      useIpAliases:                 false,
      minDiskFromImage:             DEFAULT_MIN_DISK,
      originalOpenPort:             this.value.openPort || [],
      originalMachineImage:         this.value.machineImage,
      diskSizeRequirementsFromType: null,
      fvFormRuleSets:               [
        { path: 'machineImage', rules: ['required'] },
        { path: 'diskType', rules: ['required'] },
        { path: 'diskSize', rules: ['required', 'isPositive', 'minDiskFromImageSize'] },
        { path: 'machineType', rules: ['required'] },
        { path: 'network', rules: ['required'] },
      ]
    };
  },
  created() {
    if (this.mode === _CREATE) {
      this.$emit('validationChanged', false);
      this.value.project = this.projectId;
      for (const key in this.defaultConfig) {
        if (this.value[key] === undefined && !!this.defaultConfig[key]) {
          this.value[key] = this.defaultConfig[key];
        }
      }
      merge(this.value, this.defaultConfig);
    } else {
      this.value.setInternalFirewallRulePrefix = !!this.value.internalFirewallRulePrefix;
      this.value.setExternalFirewallRulePrefix = !!this.value.externalFirewallRulePrefix;
    }
  },

  watch: {
    credentialId() {
      this.$fetch();
    },
    fvFormIsValid(newVal) {
      this.$emit('validationChanged', !!newVal);
    },

    'value.zone'() {
      this.getOptions();
    },
    'value.availabilityZone'(neu) {
      if (neu && (!this.value.managedDisks || !this.value.enablePublicIpStandardSku || !this.value.staticPublicIp)) {
        this.$emit('expandAdvanced');
      }
    },
    'value.setExternalFirewallRulePrefix'(neu) {
      if (!neu) {
        this.value.openPort = [];
      } else if (this.isCreate) {
        this.value.openPort.push('6443');
      } else {
        this.value.openPort = this.originalOpenPort.length > 0 ? this.originalOpenPort : ['6443'];
      }
    },

    networkOptions(neu) {
      if (neu && neu.length && !this.value.network) {
        const defaultNetwork = neu.find((network) => network?.name === 'default');

        if (defaultNetwork) {
          this.value.network = defaultNetwork.name;
        } else {
          const firstnetwork = neu.find((network) => network.kind !== 'group');

          this.value.network = firstnetwork.name;
        }
      }
    }
  },

  computed: {
    ...mapGetters({ t: 'i18n/t' }),
    fvExtraRules() {
      return {
        minDiskFromImageSize: (val) => {
          let minTotal = Number(this.minDiskFromImage);
          let maxTotal = null;
          const valAsNumber = Number(val || 0);

          if ( !!this.diskSizeRequirementsFromType) {
            const vals = this.diskSizeRequirementsFromType.split('-');
            const minFromType = vals[0]?.substring(0, vals[0]?.length - 2);
            const maxFromType = vals[1]?.substring(0, vals[1]?.length - 2);

            minTotal = minFromType > minTotal ? Number(minFromType) : minTotal;
            maxTotal = Number(maxFromType);
          }
          const valLessThanMin = valAsNumber < minTotal;
          const valMoreThanMax = !!maxTotal && valAsNumber > maxTotal;

          if (!maxTotal) {
            return val && valLessThanMin ? this.t('cluster.machineConfig.gce.error.diskSizeWithoutMax', { diskSizeMin: minTotal }) : undefined;
          } else {
            return val && (valLessThanMin || valMoreThanMax) ? this.t('cluster.machineConfig.gce.error.diskSizeWithMax', { diskSizeMin: minTotal, diskSizeMax: maxTotal }) : undefined;
          }
        }

      };
    },
    location() {
      return { zone: this.value.zone };
    },
    project() {
      return this.value.project;
    },

    sharedNetworks() {
      return formatSharedNetworks(this.sharedSubnetworks);
    },
    networkOptions() {
      const out = formatNetworkOptions(this.t, this.networks, this.subnetworks, this.sharedNetworks );

      return out;
    },

    subnetworkOptions() {
      return formatSubnetworkOptions(this.t, this.value.network, this.subnetworks, this.sharedNetworks, this.useIpAliases);
    },

    selectedNetwork: {
      get() {
        const { network } = this.value;

        if (this.isView) {
          return network;
        }
        if (!network) {
          return undefined;
        }

        return this.networkOptions.find((n) => n.name === network);
      },
      set(neu) {
        this.value.network = neu.name;
      }
    },

    selectedSubnetwork: {
      get() {
        const { subnetwork } = this.value;

        if (this.isView) {
          return subnetwork;
        }
        if (!subnetwork || subnetwork === '') {
          return { label: this.t('gke.subnetwork.auto'), name: GKE_NONE_OPTION };
        }

        return this.subnetworkOptions.find((n) => n.name === subnetwork);
      },
      set(neu) {
        if (neu.name === GKE_NONE_OPTION) {
          this.value.subnetwork = '';
        } else {
          this.value.subnetwork = neu.name;
        }
      }
    },
    diskType: {
      get() {
        return this.value?.diskType || '';
      },
      set(neu) {
        this.value.diskType = neu.name;
        this.diskSizeRequirementsFromType = neu.validDiskSize;
      }
    },
    tags: {
      get() {
        return this.value?.tags ? this.value.tags.split(',') : [];
      },
      set(neu) {
        this.value.tags = neu.toString();
      }
    },
    scopes: {
      get() {
        return this.value?.scopes ? this.value.scopes.split(',') : [];
      },
      set(neu) {
        this.value.scopes = neu.toString();
      }
    },
    labels: {
      get() {
        const labels = this.value.vmLabels || '';

        return convertStringToKV(labels);
      },
      set(neu) {
        this.value.vmLabels = convertKVToString(neu);
      }
    }
  },
  methods: {
    stringify,
    async getZones() {
      try {
        const res = await getGKEZones(this.$store, this.credentialId, this.project, {});

        this.zones = sortBy((res.items || []).map((z) => {
          z.disabled = z?.status?.toLowerCase() !== 'up';
          z.sortName = sortableNumericSuffix(z.name);

          return z.name;
        }), 'sortName', false);
      } catch (e) {
        this.errors.push(e.data);

        return '';
      }
    },

    async getDiskTypes() {
      this.loadingDiskTypes = true;
      try {
        const res = await getGKEDiskTypes(this.$store, this.credentialId, this.project, this.location);

        this.diskTypes = res.items.map((type) => {
          return { name: type.name, validDiskSize: type.validDiskSize };
        });
        const cur = this.diskTypes.find((el) => el.name === this.value.diskType);

        if (!cur ) {
          // If default is not actually available, reset
          if (this.isCreate) {
            this.value.diskType = '';
          }
        } else {
          this.diskSizeRequirementsFromType = cur.validDiskSize;
        }
      } catch (e) {
        this.errors.push(e.data);
      }

      this.loadingDiskTypes = false;
    },
    async getNetworks() {
      this.loadingNetworks = true;
      try {
        const res = await getGKENetworks(this.$store, this.credentialId, this.project, this.location);

        this.networks = res?.items;
      } catch (e) {
        this.errors.push(e.data);
      }
      this.loadingNetworks = false;
    },
    async getSharedSubnetworks() {
      try {
        const res = await getGKESharedSubnetworks(this.$store, this.credentialId, this.project, this.location);

        this.sharedSubnetworks = res?.subnetworks || [];
      } catch (e) {
        this.errors.push(e.data);
      }
    },
    async getSubnetworks() {
      const region = `${ this.value.zone.split('-')[0] }-${ this.value.zone.split('-')[1] }`;

      try {
        const res = await getGKESubnetworks(this.$store, this.credentialId, this.project, { region });

        this.subnetworks = res?.items || [];
      } catch (e) {
        this.errors.push(e.data);
      }
    },
    async getMachineTypes() {
      this.loadingMachineTypes = true;
      try {
        const res = await getGKEMachineTypes(this.$store, this.credentialId, this.project, this.location);

        this.machineTypes = res?.items.map((type) => {
          return type.name;
        });
      } catch (e) {
        this.errors.push(e.data);
      }
      this.loadingMachineTypes = false;
    },

    async getOptions() {
      await this.getDiskTypes();
      await this.getMachineTypes();
      await this.getNetworks();
      // These can finish loading later
      this.getSubnetworks();
      this.getSharedSubnetworks();
    },
    closeError(index) {
      this.errors = this.errors.filter((_, i) => i !== index);
    }
  }
};
</script>

<template>
  <Loading
    v-if="$fetchState.pending"
    :delayed="true"
  />

  <div v-else>
    <div v-if="errors.length">
      <div
        v-for="(err, idx) in errors"
        :key="idx"
      >
        <Banner
          color="error"
          :label="stringify(err)"
          :closable="true"
          :data-testid="`gce-error-banner-${idx}`"
          @close="closeError(idx)"
        />
      </div>
    </div>
    <div>
      <div class="col span-6">
        <LabeledSelect
          v-model:value="value.zone"
          label-key="cluster.machineConfig.gce.location.zone.label"
          :mode="mode"
          :options="zones"

          :loading="loadingZones"
          data-testid="gce-zone-select"
          class="span-3 mr-10"
          required
        />
      </div>
      <GCEImage
        v-model:value="value.machineImage"
        :credentialId="credentialId"
        :projectId="value.project"
        :originalMachineImage="originalMachineImage"
        :mode="mode"
        :location="location"
        :rules="{machineImage: fvGetAndReportPathRules('machineImage')}"
        @min-disk-changed="(val)=>minDiskFromImage=val"
        @error="(val)=>errors.push(val)"
      />

      <div class="row mt-20">
        <LabeledSelect
          v-model:value="diskType"
          label-key="cluster.machineConfig.gce.diskType.label"
          :mode="mode"
          :options="diskTypes"
          :loading="loadingDiskTypes"
          option-key="name"
          option-label="name"
          data-testid="gce-disk-type-select"
          required
          class="span-3 mr-10"
          :rules="fvGetAndReportPathRules('diskType')"
        />
        <LabeledInput
          v-model:value="value.diskSize"
          :mode="mode"
          label-key="cluster.machineConfig.gce.diskSize.label"
          :placeholder="50"
          data-testid="gce-disk-size-input"
          class="span-3 mr-10"
          required
          :rules="fvGetAndReportPathRules('diskSize')"
        />
        <LabeledSelect
          v-model:value="value.machineType"
          label-key="cluster.machineConfig.gce.machineType.label"
          :mode="mode"
          :options="machineTypes"
          :loading="loadingMachineTypes"
          data-testid="gce-machine-type-select"
          required
          :rules="fvGetAndReportPathRules('machineType')"
        />
      </div>
      <div class="row mt-20">
        <LabeledSelect
          v-model:value="selectedNetwork"
          label-key="cluster.machineConfig.gce.network.label"
          :mode="mode"
          :options="networkOptions"
          :disabled="!isCreate"
          option-key="name"
          option-label="label"
          :loading="loadingNetworks"
          data-testid="gce-network-select"
          class="span-3 mr-10"
          required
          :rules="fvGetAndReportPathRules('network')"
        />
        <LabeledSelect
          v-model:value="selectedSubnetwork"
          label-key="cluster.machineConfig.gce.subnetwork.label"
          :mode="mode"
          :options="subnetworkOptions"
          :disabled="!isCreate"
          option-key="name"
          option-label="name"
          :loading="loadingNetworks"
          data-testid="gce-subnetwork-select"
        />
      </div>
    </div>
    <portal :to="'advanced-' + uuid">
      <div class="row mt-20">
        <LabeledInput
          v-model:value="value.username"
          :mode="mode"
          label-key="cluster.machineConfig.gce.username.label"
          :placeholder="t('cluster.machineConfig.gce.username.placeholder')"
          :tooltip="t('cluster.machineConfig.gce.username.tooltip')"
          data-testid="gce-username-input"
          class="span-3 mr-10"
        />

        <LabeledInput
          v-model:value="value.address"
          :mode="mode"
          label-key="cluster.machineConfig.gce.address.label"
          :placeholder="t('cluster.machineConfig.gce.address.placeholder')"
          :tooltip="t('cluster.machineConfig.gce.address.tooltip')"
          data-testid="gce-address-input"
          class="span-3"
        />
      </div>
      <Checkbox
        v-model:value="value.preemptible"
        :mode="mode"
        :label="t('cluster.machineConfig.gce.preemptible.label')"
        :tooltip="t('cluster.machineConfig.gce.preemptible.tooltip')"
        class="mt-20"
      />

      <ArrayList
        v-model:value="scopes"
        table-class="fixed"
        :mode="mode"
        :title="t('cluster.machineConfig.gce.scopes.label')"
        :add-label="t('cluster.machineConfig.gce.scopes.add')"
        :disabled="disabled"
        class="col mt-20 span-10"
        data-testid="gce-scopes-array"
      />
      <h3 class="mt-20">
        {{ t('cluster.machineConfig.gce.firewall.header') }}
      </h3>
      <div class="row mt-20 span-12">
        <div class="col span-6">
          <Checkbox
            v-model:value="value.setInternalFirewallRulePrefix"
            :mode="mode"
            :label="t('cluster.machineConfig.gce.internalFirewall.label')"
            :tooltip="t('cluster.machineConfig.gce.internalFirewall.tooltip')"
            data-testid="gce-internal-firewall-prefix-checkbox"
          />
          <Banner
            color="info"
            label-key="cluster.machineConfig.gce.internalFirewall.banner"
            data-testid="gce-internal-firewall-banner"
          />
          <ArrayList
            v-model:value="tags"
            :mode="mode"
            :title="t('gke.tags.label')"
            :add-label="t('gke.tags.add')"
            class="col mr-10"
            data-testid="gce-tags-array"
          />
        </div>
        <div class="col span-6">
          <Checkbox
            v-model:value="value.setExternalFirewallRulePrefix"
            :mode="mode"
            :label="t('cluster.machineConfig.gce.externalFirewall.label')"
            :tooltip="t('cluster.machineConfig.gce.externalFirewall.tooltip')"
            data-testid="gce-external-firewall-prefix-checkbox"
          />
          <div v-if="!!value.setExternalFirewallRulePrefix">
            <Banner
              color="info"
              label-key="cluster.machineConfig.gce.externalFirewall.banner"
              data-testid="gce-external-firewall-banner"
            />
            <ArrayList
              v-model:value="value.openPort"
              :mode="mode"
              :title="t('cluster.machineConfig.gce.openPort.label')"
              :add-label="t('cluster.machineConfig.gce.openPort.add')"
              class="col"
              data-testid="gce-ports-array"
            />
          </div>
        </div>
      </div>
      <div class="mt-20">
        <h3>
          <t k="labels.labels.title" />
        </h3>
        <KeyValue
          v-model:value="labels"
          :mode="mode"
          :value-can-be-empty="true"
          :add-label="t('aks.nodePools.labels.add')"
          :read-allowed="false"
          data-testid="gce-labels-kv"
        />
      </div>
    </portal>
  </div>
</template>
